using System;
using System.Text;
using System.Runtime.InteropServices;

namespace KodeIT.Web
{
    internal static class NativeMethods
    {
        private const string NATIVE_DLL = "filter_isapi.dll";

        internal const int SF_MAX_USERNAME = 0x101;
        internal const int SF_MAX_PASSWORD = 0x101;
        internal const int SF_FIXEDBUFFER = 0x400;

        internal const int ERROR_INSUFFICIENT_BUFFER = 122;
        internal const int ERROR_INVALID_INDEX = 1413;
        internal const int ERROR_INVALID_PARAMETER = 87;
        internal const int ERROR_NOT_SUPPORTED = 50;
        internal const int FORMAT_MESSAGE_FROM_SYSTEM = 4096;

        internal const int MIN_HEADER_COUNT = 30;
        internal const int MIN_VARIABLE_COUNT = 30;

        [DllImport("Kernel32.dll", CharSet = CharSet.Ansi)]
        private static extern int FormatMessage(int flags, IntPtr sourcePtr, int errorCode, int languageId, StringBuilder buffer, int len, string[] arguments);

        [DllImport(NATIVE_DLL, CharSet = CharSet.Ansi, SetLastError = true)]
        private static extern bool Cb_IsSecure(IntPtr contextPtr);

        [DllImport(NATIVE_DLL, CharSet = CharSet.Ansi, SetLastError = true)]
        private static extern bool Cb_GetVariable(IntPtr contextPtr, string variableName, StringBuilder variableValueBuffer, ref int variableValueLen);

        [DllImport(NATIVE_DLL, CharSet = CharSet.Ansi, SetLastError = true)]
        private static extern bool Cb_GetHeader(IntPtr contextPtr, IntPtr eventPtr, FilterEvent eventType, string headerName, StringBuilder headerValueBuffer, ref int headerValueLen);

        [DllImport(NATIVE_DLL, CharSet = CharSet.Ansi, SetLastError = true)]
        private static extern bool Cb_SetHeader(IntPtr contextPtr, IntPtr eventPtr, FilterEvent eventType, string headerName, string headerValue);

        [DllImport(NATIVE_DLL, CharSet = CharSet.Ansi)]
        private static extern void Cb_GetUrlMap(IntPtr eventPtr, StringBuilder urlBuffer, int urlLength, StringBuilder pathBuffer, int pathLength);

        [DllImport(NATIVE_DLL, CharSet = CharSet.Ansi)]
        private static extern void Cb_SetUrlMap(IntPtr eventPtr, string pathBuffer);

        [DllImport(NATIVE_DLL, CharSet = CharSet.Ansi)]
        private static extern void Cb_GetUserPass(IntPtr eventPtr, StringBuilder userBuffer, int userLen, StringBuilder passBuffer, int passLen);

        [DllImport(NATIVE_DLL, CharSet = CharSet.Ansi)]
        private static extern void Cb_SetUserPass(IntPtr eventPtr, string userBuffer, string passBuffer);

        [DllImport(NATIVE_DLL, CharSet = CharSet.Ansi)]
        private static extern void Cb_GetAccessDenied(IntPtr eventPtr, StringBuilder urlBuffer, int urlLength, StringBuilder pathBuffer, int pathLength);

        [DllImport(NATIVE_DLL, CharSet = CharSet.Ansi, SetLastError = true)]
        private unsafe static extern bool Cb_WriteClient(IntPtr contextPtr, byte* bytesBuffer, int bytesLen);

        [DllImport(NATIVE_DLL, CharSet = CharSet.Ansi)]
        private unsafe static extern void Cb_ReadRaw(IntPtr eventPtr, byte* bytesBuffer, int bytesLen);

        [DllImport(NATIVE_DLL, CharSet = CharSet.Ansi, SetLastError = true)]
        private unsafe static extern bool Cb_WriteRaw(IntPtr contextPtr, IntPtr eventPtr, byte* bytesBuffer, int bytesLen);

        [DllImport(NATIVE_DLL, CharSet = CharSet.Ansi)]
        private static extern void Cb_GetLog(
            IntPtr eventPtr, 
            StringBuilder clientHostNameBuffer,
            int clientHostNameLength,
            StringBuilder clientUserNameBuffer,
            int clientUserNameLength,
            StringBuilder serverNameBuffer,
            int serverNameLength,
            StringBuilder operationBuffer,
            int operationLength,
            StringBuilder targetBuffer,
            int targetLength,
            StringBuilder parametersBuffer,
            int parametersLength);

        [DllImport(NATIVE_DLL, CharSet = CharSet.Ansi, SetLastError = true)]
        private static extern bool Cb_SetLog(
            IntPtr contextPtr, 
            IntPtr eventPtr,
            string clientHostNameBuffer,
            string clientUserNameBuffer,
            string serverNameBuffer,
            string operationBuffer,
            string targetBuffer,
            string parametersBuffer,
            int dwHttpStatus,
            int dwWin32Status,
            int dwBytesSent,
            int dwBytesRecvd,
            int msTimeForProcessing);

        internal static bool IsSecure(NativeContext nativeContext)
        {
            return Cb_IsSecure(nativeContext.ContextPtr);
        }

        internal static string GetVariable(NativeContext nativeContext, string variableName)
        {
            StringBuilder sb = new StringBuilder(NativeMethods.SF_FIXEDBUFFER);
            int sbLen = sb.Capacity;
            bool isOk = NativeMethods.Cb_GetVariable(nativeContext.ContextPtr, variableName, sb, ref sbLen);
            int nativeError = Marshal.GetLastWin32Error();
            
            if (isOk)
            {
                return sb.ToString();
            }

            if (ERROR_INSUFFICIENT_BUFFER == nativeError)
            {
                sb = new StringBuilder(sbLen);
                isOk = NativeMethods.Cb_GetVariable(nativeContext.ContextPtr, variableName, sb, ref sbLen);
                nativeError = Marshal.GetLastWin32Error();

                if (isOk)
                {
                    return sb.ToString();
                }
            }

            if (ERROR_INVALID_INDEX == nativeError)
            {
                return null;
            }
            else
            {
                throw new NativeException(nativeError);
            }
        }

        internal static string GetHeader(NativeContext nativeContext, string headerName)
        {
            StringBuilder sb = new StringBuilder(NativeMethods.SF_FIXEDBUFFER);
            int sbLen = sb.Capacity;
            bool isOk = NativeMethods.Cb_GetHeader(
                nativeContext.ContextPtr,
                nativeContext.EventPtr,
                nativeContext.EventType, 
                headerName, 
                sb, 
                ref sbLen);
            int nativeError = Marshal.GetLastWin32Error();

            if (isOk)
            {
                return sb.ToString();
            }

            if (ERROR_INSUFFICIENT_BUFFER == nativeError)
            {
                sb = new StringBuilder(sbLen);
                isOk = NativeMethods.Cb_GetHeader(
                    nativeContext.ContextPtr,
                    nativeContext.EventPtr,
                    nativeContext.EventType, 
                    headerName, 
                    sb, 
                    ref sbLen);
                nativeError = Marshal.GetLastWin32Error();
                
                if (isOk)
                {
                    return sb.ToString();
                }
            }

            if (ERROR_INVALID_INDEX == nativeError)
            {
                return null;
            }
            else
            {
                throw new NativeException(nativeError);
            }
        }

        internal static void SetHeader(NativeContext nativeContext, string headerName, string headerValue)
        {
            bool isOk = NativeMethods.Cb_SetHeader(
                nativeContext.ContextPtr,
                nativeContext.EventPtr,
                nativeContext.EventType, 
                headerName, 
                headerValue);
            int nativeError = Marshal.GetLastWin32Error();

            if (!isOk)
            {
                if (ERROR_INVALID_PARAMETER == nativeError)
                {
                    throw new MalformedHeaderException(headerName, nativeError);
                }
                else if (ERROR_NOT_SUPPORTED == nativeError)
                {
                    throw new NotSupportedHeaderException(headerName, nativeError);
                }
                else
                {
                    throw new NativeException(nativeError);
                }
            }
        }

        internal static void GetUrlMap(NativeContext nativeContext, NativeString url, NativeString path)
        {
            NativeMethods.Cb_GetUrlMap(
                nativeContext.EventPtr, 
                url.Builder, 
                url.NativeLength, 
                path.Builder, 
                path.NativeLength);
        }

        internal static void SetUrlMap(NativeContext nativeContext, string pathBuffer)
        {
            NativeMethods.Cb_SetUrlMap(
                nativeContext.EventPtr, 
                pathBuffer);
        }

        internal static void GetUserPass(NativeContext nativeContext, NativeString user, NativeString pass)
        {
            NativeMethods.Cb_GetUserPass(
                nativeContext.EventPtr, 
                user.Builder, 
                user.NativeLength, 
                pass.Builder, 
                pass.NativeLength);
        }

        internal static void SetUserPass(NativeContext nativeContext, string userBuffer, string passBuffer)
        {
            NativeMethods.Cb_SetUserPass(
                nativeContext.EventPtr, 
                userBuffer, 
                passBuffer);
        }

        internal static void GetAccessDenied(NativeContext nativeContext, NativeString url, NativeString path)
        {
            NativeMethods.Cb_GetAccessDenied(
                nativeContext.EventPtr, 
                url.Builder, 
                url.NativeLength, 
                path.Builder, 
                path.NativeLength);
        }

        internal static void WriteClient(NativeContext nativeContext, byte[] bytes)
        {
            unsafe
            {
                fixed (byte* bytesUnsafe = bytes)
                {
                    if (!Cb_WriteClient(nativeContext.ContextPtr, bytesUnsafe, bytes.Length))
                    {
                        throw new NativeException(Marshal.GetLastWin32Error());
                    }
                }
            }
        }

        internal static void ReadRaw(NativeContext nativeContext, byte[] bytes)
        {
            unsafe
            {
                fixed (byte* bytesUnsafe = bytes)
                {
                    Cb_ReadRaw(nativeContext.EventPtr, bytesUnsafe, bytes.Length);
                }
            }
        }

        internal static void WriteRaw(NativeContext nativeContext, byte[] bytes)
        {
            unsafe
            {
                fixed (byte* bytesUnsafe = bytes)
                {
                    if (!Cb_WriteRaw(nativeContext.ContextPtr, nativeContext.EventPtr, bytesUnsafe, bytes.Length))
                    {
                        throw new NativeException(Marshal.GetLastWin32Error());
                    }
                }
            }
        }

        internal static void GetLog(NativeContext nativeContext, LogBuffers logBuffers)
        {
            Cb_GetLog(
                nativeContext.EventPtr,
                logBuffers._clientHostName.Builder,
                logBuffers._clientHostName.NativeLength,
                logBuffers._clientUserName.Builder,
                logBuffers._clientUserName.NativeLength,
                logBuffers._serverName.Builder,
                logBuffers._serverName.NativeLength,
                logBuffers._operation.Builder,
                logBuffers._operation.NativeLength,
                logBuffers._target.Builder,
                logBuffers._target.NativeLength,
                logBuffers._parameters.Builder,
                logBuffers._parameters.NativeLength);
        }

        internal static void SetLog(NativeContext nativeContext, LogBuffers logBuffers,
            int dwHttpStatus, int dwWin32Status, int dwBytesSent, int dwBytesRecvd, int msTimeForProcessing)
        {
            if (!Cb_SetLog(
                nativeContext.ContextPtr,
                nativeContext.EventPtr,
                logBuffers._clientHostName.Setted ? logBuffers._clientHostName.Data : null,
                logBuffers._clientUserName.Setted ? logBuffers._clientUserName.Data : null,
                logBuffers._serverName.Setted ? logBuffers._serverName.Data : null,
                logBuffers._operation.Setted ? logBuffers._operation.Data : null,
                logBuffers._target.Setted ? logBuffers._target.Data : null,
                logBuffers._parameters.Setted ? logBuffers._parameters.Data : null,
                dwHttpStatus, 
                dwWin32Status, 
                dwBytesSent, 
                dwBytesRecvd, 
                msTimeForProcessing))
            {
                throw new NativeException(Marshal.GetLastWin32Error());
            }
        }

        internal static string FormatMessage(int nativeError)
        {
            StringBuilder errorBuffer = new StringBuilder(SF_FIXEDBUFFER);
            if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, IntPtr.Zero, nativeError, 0, errorBuffer, errorBuffer.Capacity, null) == 0)
            {
                return String.Format("Unknown error code {0}", nativeError);
            }

            int length = errorBuffer.Length;
            while (length > 0)
            {
                char ch = errorBuffer[length - 1];
                if ((ch > ' ') && (ch != '.'))
                {
                    break;
                }
                length--;
            }
            return errorBuffer.ToString(0, length);
        }
    }
}
